use crate::avm2::object::BitmapDataObject;
use crate::context::RenderContext;
use crate::display_object::{DisplayObject, DisplayObjectWeak, TDisplayObject};
use bitflags::bitflags;
use gc_arena::lock::GcRefLock;
use gc_arena::{Collect, Gc, Mutation};
use ruffle_render::backend::RenderBackend;
use ruffle_render::bitmap::{
    Bitmap, BitmapFormat, BitmapHandle, PixelRegion, PixelSnapping, SyncHandle,
};
use ruffle_wstr::WStr;
use std::cell::Ref;
use std::fmt::Debug;
use std::ops::Range;
use swf::{Rectangle, Twips};
use tracing::instrument;

/// An implementation of the Lehmer/Park-Miller random number generator
/// Uses the fixed parameters m = 2,147,483,647 and a = 16,807
pub struct LehmerRng {
    x: u32,
}

impl LehmerRng {
    pub fn with_seed(seed: u32) -> Self {
        Self { x: seed }
    }

    /// Generate the next value in the sequence via the following formula
    /// X_(k+1) = a * X_k mod m
    pub fn random(&mut self) -> u32 {
        self.x = ((self.x as u64).overflowing_mul(16_807).0 % 2_147_483_647) as u32;
        self.x
    }

    pub fn random_range(&mut self, rng: Range<u8>) -> u8 {
        rng.start + (self.random() % ((rng.end - rng.start) as u32 + 1)) as u8
    }
}

/// This can represent both a premultiplied and an unmultiplied ARGB color value.
///
/// Note that most operations only make sense on one of these representations:
/// For example, blending on premultiplied values, and applying a `ColorTransform` on
/// unmultiplied values. Make sure to convert the color to the correct form beforehand.
// TODO: Maybe split the type into `PremultipliedColor(u32)` and
//   `UnmultipliedColor(u32)`?
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, bytemuck::NoUninit)]
#[repr(C, align(4))]
pub struct Color {
    // Note: even though AS2/AS3 represent colors as (little-endian) BGRA `u32`s, this is stored
    // in RGBA order to be compatible with what the render backend expects.
    // TODO: consider switching renderers to use BGRA to avoid byteswaps when talking to ActionScript?
    r: u8,
    g: u8,
    b: u8,
    a: u8,
}

#[derive(Debug, Clone)]
pub enum BitmapDataDrawError {
    Unimplemented,
}

impl Color {
    #[must_use]
    pub fn rgba(r: u8, g: u8, b: u8, a: u8) -> Self {
        Self { r, g, b, a }
    }

    #[must_use]
    pub fn bgra_u32(bgra: u32) -> Self {
        let [b, g, r, a] = bgra.to_le_bytes();
        Self { r, g, b, a }
    }

    pub fn to_bgra_u32(&self) -> u32 {
        u32::from_le_bytes([self.b, self.g, self.r, self.a])
    }

    pub fn blue(&self) -> u8 {
        self.b
    }

    pub fn green(&self) -> u8 {
        self.g
    }

    pub fn red(&self) -> u8 {
        self.r
    }

    pub fn alpha(&self) -> u8 {
        self.a
    }

    #[must_use]
    pub fn to_premultiplied_alpha(self, transparency: bool) -> Self {
        // This has some accuracy issues with some alpha values

        let old_alpha = if transparency { self.alpha() } else { 255 };

        let a = old_alpha as u32;
        let r = ((self.red() as u32 * a + 127) / 255) as u8;
        let g = ((self.green() as u32 * a + 127) / 255) as u8;
        let b = ((self.blue() as u32 * a + 127) / 255) as u8;

        Self::rgba(r, g, b, old_alpha)
    }

    #[must_use]
    pub fn to_un_multiplied_alpha(self) -> Self {
        // We need to match Flash's results, and this lookup table was generated by brute force.
        // For each alpha value, every value between 0..256^3 was tested to see if it produced the
        // correct color value when reversing the premultiplication.
        // Source code used to generate this table can be found at:
        // https://gist.github.com/pdewacht/614b428cd42c2052dc0fd292516c9f9f
        const FLASH_PREMUL_FACTOR: [u32; 256] = [
            0, 16678912, 8339456, 5559638, 4169728, 3335783, 2779819, 2386603, 2086230, 1855488,
            1667892, 1518251, 1391151, 1285234, 1193302, 1111928, 1043895, 981113, 927744, 879275,
            834621, 795535, 759126, 726358, 695839, 668183, 642538, 618737, 596651, 576171, 555964,
            538706, 522104, 506319, 490557, 477321, 464038, 451353, 439544, 428244, 417582, 407500,
            397768, 388535, 379630, 371117, 363179, 355235, 348050, 340965, 334052, 327038, 321269,
            315077, 309159, 303586, 298189, 293092, 287981, 283080, 278251, 273892, 269268, 265179,
            261087, 256971, 253160, 249322, 245508, 242164, 238575, 235245, 231859, 228848, 225785,
            222712, 219616, 216827, 213985, 211432, 208835, 206075, 203750, 201196, 198895, 196223,
            194301, 191987, 189686, 187636, 185559, 183426, 181453, 179444, 177638, 175855, 174054,
            171948, 170489, 168695, 166889, 165365, 163519, 162045, 160508, 158970, 157429, 156150,
            154610, 153081, 151803, 150511, 148986, 147709, 146420, 145116, 143868, 142586, 141545,
            140277, 139194, 137957, 136954, 135676, 134652, 133621, 132604, 131577, 130552, 129527,
            128508, 127476, 126451, 125432, 124670, 123645, 122818, 121847, 121082, 120060, 119288,
            118263, 117502, 116720, 115967, 115195, 114424, 113655, 112893, 112125, 111356, 110563,
            109811, 109048, 108287, 107766, 107004, 106236, 105724, 104953, 104434, 103676, 102904,
            102375, 101879, 101119, 100604, 99834, 99321, 98813, 98112, 97533, 97019, 96509, 95994,
            95486, 94713, 94185, 93689, 93179, 92667, 92149, 91643, 91129, 90621, 90068, 89597,
            89342, 88829, 88318, 87804, 87294, 87034, 86523, 85994, 85499, 85245, 84732, 84222,
            83956, 83450, 82937, 82685, 82173, 81840, 81405, 80889, 80638, 80127, 79862, 79354,
            79103, 78590, 78332, 78077, 77565, 77308, 76795, 76541, 76284, 75766, 75518, 75262,
            74748, 74493, 74238, 73691, 73470, 73214, 72959, 72447, 72189, 71935, 71671, 71166,
            70911, 70651, 70399, 70140, 69886, 69615, 69116, 68861, 68603, 68350, 68093, 67839,
            67576, 67326, 67070, 66813, 66556, 66302, 66046, 65791, 65408,
        ];

        let alpha_factor = FLASH_PREMUL_FACTOR[self.alpha() as usize];
        let unmultiply = |c| ((c as u32 * alpha_factor + 0x8000) >> 16) as u8;

        let r = unmultiply(self.red());
        let g = unmultiply(self.green());
        let b = unmultiply(self.blue());

        Self::rgba(r, g, b, self.alpha())
    }

    #[must_use]
    pub fn with_alpha(&self, alpha: u8) -> Self {
        Self::rgba(self.red(), self.green(), self.blue(), alpha)
    }

    /// # Arguments
    ///
    /// * `self` - Must be in premultiplied form.
    /// * `source` - Must be in premultiplied form.
    #[must_use]
    pub fn blend_over(&self, source: &Self) -> Self {
        let sa = source.alpha();

        let r = source.red() + ((self.red() as u16 * (255 - sa as u16)) / 255) as u8;
        let g = source.green() + ((self.green() as u16 * (255 - sa as u16)) / 255) as u8;
        let b = source.blue() + ((self.blue() as u16 * (255 - sa as u16)) / 255) as u8;
        let a = source.alpha() + ((self.alpha() as u16 * (255 - sa as u16)) / 255) as u8;
        Self::rgba(r, g, b, a)
    }

    fn slice_as_rgba(slice: &[Self]) -> &[u8] {
        bytemuck::cast_slice(slice)
    }
}

impl std::fmt::Display for Color {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_str(&format!("{:#x}", self.to_bgra_u32()))
    }
}

impl From<Color> for u32 {
    fn from(c: Color) -> Self {
        c.to_bgra_u32()
    }
}

impl From<u32> for Color {
    fn from(i: u32) -> Self {
        Color::bgra_u32(i)
    }
}

impl From<swf::Color> for Color {
    fn from(c: swf::Color) -> Self {
        Self::rgba(c.r, c.g, c.b, c.a)
    }
}

impl From<Color> for swf::Color {
    fn from(c: Color) -> Self {
        let r = c.red();
        let g = c.green();
        let b = c.blue();
        let a = c.alpha();
        Self { r, g, b, a }
    }
}

bitflags! {
    pub struct ChannelOptions: u8 {
        const RED = 1 << 0;
        const GREEN = 1 << 1;
        const BLUE = 1 << 2;
        const ALPHA = 1 << 3;
        const RGB = Self::RED.bits() | Self::GREEN.bits() | Self::BLUE.bits();
    }
}

#[derive(Copy, Clone, Collect, Debug)]
#[collect(no_drop)]
pub struct BitmapData<'gc>(BitmapRawDataWrapper<'gc>);

impl<'gc> BitmapData<'gc> {
    pub fn new(
        mc: &Mutation<'gc>,
        width: u32,
        height: u32,
        transparency: bool,
        fill_color: u32,
    ) -> Self {
        let data = BitmapRawData::new(width, height, transparency, fill_color);
        let data = BitmapRawDataWrapper::new(Gc::new(mc, data.into()));

        Self(data)
    }

    pub fn new_with_pixels(
        mc: &Mutation<'gc>,
        width: u32,
        height: u32,
        transparency: bool,
        pixels: Vec<Color>,
    ) -> Self {
        let data = BitmapRawData::new_with_pixels(width, height, transparency, pixels);
        let data = BitmapRawDataWrapper::new(Gc::new(mc, data.into()));

        Self(data)
    }

    pub fn dummy(mc: &Mutation<'gc>) -> Self {
        Self(BitmapRawDataWrapper::dummy(mc))
    }

    pub fn clone_data(
        &self,
        mc: &Mutation<'gc>,
        renderer: &mut dyn RenderBackend,
    ) -> BitmapData<'gc> {
        let data = self.0.clone_data(renderer);
        let data = BitmapRawDataWrapper::new(Gc::new(mc, data.into()));
        Self(data)
    }

    pub fn sync(&self, renderer: &mut dyn RenderBackend) -> GcRefLock<'gc, BitmapRawData<'gc>> {
        self.0.sync(renderer)
    }

    pub fn bitmap_handle(
        &self,
        gc_context: &Mutation<'gc>,
        renderer: &mut dyn RenderBackend,
    ) -> BitmapHandle {
        self.0.bitmap_handle(gc_context, renderer)
    }

    pub fn overwrite_cpu_pixels_from_gpu(
        &self,
        mc: &Mutation<'gc>,
    ) -> (GcRefLock<'gc, BitmapRawData<'gc>>, Option<PixelRegion>) {
        self.0.overwrite_cpu_pixels_from_gpu(mc)
    }

    pub fn read_area(
        &self,
        read_area: PixelRegion,
        renderer: &mut dyn RenderBackend,
    ) -> Ref<'_, BitmapRawData<'gc>> {
        self.0.read_area(read_area, renderer)
    }

    pub fn height(&self) -> u32 {
        self.0.height()
    }

    pub fn width(&self) -> u32 {
        self.0.width()
    }

    pub fn object2(&self) -> Option<BitmapDataObject<'gc>> {
        self.0.object2()
    }

    pub fn disposed(&self) -> bool {
        self.0.disposed()
    }

    pub fn transparency(&self) -> bool {
        self.0.transparency()
    }

    pub fn check_valid(
        &self,
        activation: &mut crate::avm2::Activation<'_, 'gc>,
    ) -> Result<(), crate::avm2::Error<'gc>> {
        self.0.check_valid(activation)
    }

    pub fn dispose(&self, mc: &Mutation<'gc>) {
        self.0.dispose(mc);
    }

    pub fn init_object2(&self, mc: &Mutation<'gc>, object: BitmapDataObject<'gc>) {
        self.0.init_object2(mc, object);
    }

    pub fn remove_display_object(&self, mc: &Mutation<'gc>, callback: DisplayObjectWeak<'gc>) {
        self.0.remove_display_object(mc, callback);
    }

    pub fn add_display_object(&self, mc: &Mutation<'gc>, callback: DisplayObjectWeak<'gc>) {
        self.0.add_display_object(mc, callback);
    }

    pub fn render(
        &self,
        smoothing: bool,
        context: &mut RenderContext<'_, 'gc>,
        pixel_snapping: PixelSnapping,
    ) {
        self.0.render(smoothing, context, pixel_snapping);
    }

    pub fn can_read(&self, read_area: PixelRegion) -> bool {
        self.0.can_read(read_area)
    }

    #[cfg(feature = "egui")]
    pub fn debug_sync_status(&self) -> std::borrow::Cow<'static, str> {
        self.0.debug_sync_status()
    }

    pub fn is_point_in_bounds(&self, x: i32, y: i32) -> bool {
        self.0.is_point_in_bounds(x, y)
    }

    pub fn ptr_eq(&self, other: BitmapData<'gc>) -> bool {
        self.0.ptr_eq(other.0)
    }
}

#[derive(Collect)]
#[collect(no_drop)]
pub struct BitmapRawData<'gc> {
    /// The pixels in the bitmap, stored as a array of pre-multiplied ARGB colour values
    #[collect(require_static)]
    pixels: Vec<Color>,

    width: u32,
    height: u32,
    transparency: bool,

    // Note that it's technically possible to have a BitmapData with zero width and height,
    // (by embedding it in the SWF instead of using the BitmapData constructor),
    // so we need a separate 'disposed' flag.
    disposed: bool,

    /// The bitmap handle for this data.
    ///
    /// This is lazily initialized; a value of `None` indicates that
    /// initialization has not yet happened.
    #[collect(require_static)]
    bitmap_handle: Option<BitmapHandle>,

    /// The AVM2 side of this `BitmapData`.
    ///
    /// AVM1 cannot retrieve `BitmapData` back from the display object tree, so
    /// this does not need to hold an AVM1 object.
    avm2_object: Option<BitmapDataObject<'gc>>,

    /// A list of display objects that are backed by this BitmapData
    display_objects: Vec<DisplayObjectWeak<'gc>>,

    #[collect(require_static)]
    dirty_state: DirtyState,

    /// Holds an egui texture handle, used for rendering this Bitmap in the debug ui.
    /// This is automatically set to `None` when the texture is updated (either from
    /// marking the CPU side dirty, or from performing a GPU -> CPU sync).
    #[cfg(feature = "egui")]
    pub egui_texture: std::cell::RefCell<Option<egui::TextureHandle>>,
}

#[derive(Clone, Debug)]
pub enum DirtyState {
    // Both the CPU and GPU pixels are up to date. We do not need to wait for any syncs to complete
    Clean,

    // The CPU pixels have been modified, and need to be synced to the GPU via `update_dirty_texture`
    CpuModified(PixelRegion),

    // The GPU pixels have been modified, and need to be synced to the CPU via `BitmapRawDataWrapper::sync`
    GpuModified(Box<dyn SyncHandle>, PixelRegion),
}

mod wrapper {
    use crate::avm2::object::BitmapDataObject;
    use crate::context::RenderContext;
    use crate::display_object::DisplayObjectWeak;
    use gc_arena::barrier::Write;
    use gc_arena::lock::GcRefLock;
    use gc_arena::{Collect, Gc, Mutation};
    use ruffle_render::backend::RenderBackend;
    use ruffle_render::bitmap::{BitmapHandle, PixelRegion, PixelSnapping};
    use ruffle_render::commands::CommandHandler;
    use std::cell::Ref;

    use super::{copy_pixels_to_bitmapdata, BitmapRawData, DirtyState};

    /// A wrapper type that ensures that we always wait for a pending
    /// GPU -> CPU sync to complete (using `sync_handle`) before accessing
    /// the CPU-side pixels.
    ///
    /// This is overly conservative - we perform a sync before allowing any access
    /// to the underlying `BitmapData`, even if we wouldn't be accessing the pixels.
    /// Implementing more fine-grained tracking turned out to be extremely invasive,
    /// and made the code much less readable. This should be enough for the simple
    /// case where ActionScript calls `BitmapData.draw`, and then doesn't interact
    /// with the Bitmap/BitmapData object at all for some time.
    ///
    /// There are three ways that this type gets used:
    /// 1. Blocking on the current GPU->CPU sync via the `sync` method,
    ///    and obtainng a `GcRefLock<'gc, BitmapData<'gc>>` (or implicily through `as_bitmap_data`).
    ///    This is done for the vast majority of BitmapData AS2/AS3 methods, as they need to access CPU-side pixels.
    /// 2. Ignoring the current GPU->CPU sync state. This is done by the `render` method defined on this type,
    ///    since rendering only uses GPU-side data, and ignores CPU-side pixels entirely.
    /// 3. Explicitly cancelling any in-progress GPU->CPU sync via `overwrite_cpu_pixels_from_gpu`. This is
    ///    used by `BitmapData.draw` and `BitmapData.apply_filter`, since the new rendering result will completely
    ///    replace the current CPU-side pixels. This performs a CPU -> GPU sync, to ensure that the GPU side
    ///    is up to date before we overwrite the CPU-side pixels.
    ///    In the future, we could explore using this in additional
    ///    cases where we know that the entire CPU-side pixel array will be overwritten without being read
    ///    (e.g. `BitmapData.fillRect` with a rectangle covering the entire bitmap). However, `overwrite_cpu_pixels`
    ///    is always a performance optimization, and can always be safely replaced with `sync` (at the cost of worse performance)
    ///
    /// Note that we also perform CPU-GPU syncs from `BitmapData.update_dirty_texture` when `dirty` is set.
    /// `sync_handle` and `dirty` can never be set at the same time - we can only have one of them set, or none of them set.
    #[derive(Copy, Clone, Collect, Debug)]
    #[collect(no_drop)]
    pub struct BitmapRawDataWrapper<'gc>(GcRefLock<'gc, BitmapRawData<'gc>>);

    impl<'gc> BitmapRawDataWrapper<'gc> {
        pub fn new(data: GcRefLock<'gc, BitmapRawData<'gc>>) -> Self {
            BitmapRawDataWrapper(data)
        }

        // Creates a dummy BitmapData with no pixels or handle, marked as disposed.
        // This is used for AS3 `Bitmap` instances without a corresponding AS3 `BitmapData` instance.
        // Marking it as disposed skips rendering, and the unset `avm2_object` will cause this to
        // be inaccessible to AS3 code.
        pub fn dummy(mc: &Mutation<'gc>) -> Self {
            BitmapRawDataWrapper(Gc::new(
                mc,
                BitmapRawData {
                    pixels: Vec::new(),
                    width: 0,
                    height: 0,
                    transparency: false,
                    disposed: true,
                    bitmap_handle: None,
                    avm2_object: None,
                    display_objects: vec![],
                    dirty_state: DirtyState::Clean,
                    #[cfg(feature = "egui")]
                    egui_texture: Default::default(),
                }
                .into(),
            ))
        }

        /// Clones the underlying data, producing a new `BitmapData`
        /// that has no GPU texture or associated display objects
        pub fn clone_data(&self, renderer: &mut dyn RenderBackend) -> BitmapRawData<'gc> {
            // Sync from the GPU to CPU, since our new BitmapData starts out
            // with no GPU texture
            let data = self.sync(renderer).borrow();
            BitmapRawData {
                pixels: data.pixels.clone(),
                width: data.width,
                height: data.height,
                transparency: data.transparency,
                disposed: data.disposed,
                bitmap_handle: None,
                avm2_object: None,
                display_objects: vec![],
                // We have no GPU texture, so there's no need to mark as dirty
                dirty_state: DirtyState::Clean,
                #[cfg(feature = "egui")]
                egui_texture: Default::default(),
            }
        }

        // Provides access to the underlying `BitmapData`. If a GPU -> CPU sync
        // is in progress, waits for it to complete
        pub fn sync(&self, renderer: &mut dyn RenderBackend) -> GcRefLock<'gc, BitmapRawData<'gc>> {
            // SAFETY: The only fields that can store gc pointers are `avm2_object` and `dirty_callbacks`,
            // which we don't update here. Ideally, we would refactor this so that
            // `BitmapData` doesn't contain any gc pointers, allowing us to use a normal
            // `RefCell` instead of a `RefLock`.
            let mut write = unsafe { Write::assume(Gc::as_ref(self.0)) }
                .unlock()
                .borrow_mut();
            match std::mem::replace(&mut write.dirty_state, DirtyState::Clean) {
                DirtyState::GpuModified(sync_handle, bounds) => {
                    renderer
                        .resolve_sync_handle(
                            sync_handle,
                            Box::new(|buffer, buffer_width| {
                                copy_pixels_to_bitmapdata(&mut write, buffer, buffer_width, bounds)
                            }),
                        )
                        .expect("Failed to sync BitmapData");
                    write.dirty_state = DirtyState::Clean;
                    #[cfg(feature = "egui")]
                    write.egui_texture.borrow_mut().take();
                }
                old_state => write.dirty_state = old_state,
            }
            self.0
        }

        /// Provides access to the underlying `BitmapHandle`.
        /// If the CPU pixels are dirty, syncs them to the GPU.
        /// If the GPU pixels are dirty, then handle is returned immediately
        /// without waiting for the sync to complete, as a BitmapHandle can
        /// only be used to access the GPU data. Unlike `overwrite_cpu_pixels_from_gpu`,
        /// this does not cancel the GPU -> CPU sync.
        pub fn bitmap_handle(
            &self,
            gc_context: &Mutation<'gc>,
            renderer: &mut dyn RenderBackend,
        ) -> BitmapHandle {
            let mut bitmap_data = self.0.borrow_mut(gc_context);
            bitmap_data.update_dirty_texture(renderer);
            bitmap_data.bitmap_handle(renderer)
        }

        /// Provides access to the underlying `BitmapData`.
        /// This should only be used when you will be overwriting the entire
        /// `pixels` vec without reading from it. Cancels any in-progress GPU -> CPU sync.
        /// This does not sync from cpu to gpu.
        pub fn overwrite_cpu_pixels_from_gpu(
            &self,
            mc: &Mutation<'gc>,
        ) -> (GcRefLock<'gc, BitmapRawData<'gc>>, Option<PixelRegion>) {
            let mut write = self.0.borrow_mut(mc);
            let dirty_rect = match write.dirty_state {
                DirtyState::GpuModified(_, rect) => {
                    write.dirty_state = DirtyState::Clean;
                    Some(rect)
                }
                DirtyState::CpuModified(_) | DirtyState::Clean => None,
            };
            #[cfg(feature = "egui")]
            write.egui_texture.borrow_mut().take();
            (self.0, dirty_rect)
        }

        /// Provides read access to the BitmapData pixels.
        /// Only the provided region is guaranteed to be up-to-date.
        /// It is an error to access any other pixels outside of that region.
        pub fn read_area(
            &self,
            read_area: PixelRegion,
            renderer: &mut dyn RenderBackend,
        ) -> Ref<'_, BitmapRawData<'gc>> {
            let needs_update = if let DirtyState::GpuModified(_, area) = self.0.borrow().dirty_state
            {
                area.intersects(read_area)
            } else {
                false
            };
            if needs_update {
                self.sync(renderer);
            }
            self.0.borrow()
        }

        // These methods do not require a sync to complete, as they do not depend on the
        // CPU-side pixels. They are implemented directly on `BitmapRawDataWrapper`, allowing
        // callers to avoid calling sync()

        pub fn height(&self) -> u32 {
            self.0.borrow().height
        }

        pub fn width(&self) -> u32 {
            self.0.borrow().width
        }

        pub fn object2(&self) -> Option<BitmapDataObject<'gc>> {
            self.0.borrow().object2()
        }

        pub fn disposed(&self) -> bool {
            self.0.borrow().disposed
        }

        pub fn transparency(&self) -> bool {
            self.0.borrow().transparency
        }

        pub fn check_valid(
            &self,
            activation: &mut crate::avm2::Activation<'_, 'gc>,
        ) -> Result<(), crate::avm2::Error<'gc>> {
            if self.disposed() {
                Err(crate::avm2::error::make_error_2015(activation))
            } else {
                Ok(())
            }
        }

        pub fn dispose(&self, mc: &Mutation<'gc>) {
            self.0.borrow_mut(mc).dispose();
        }

        pub fn init_object2(&self, mc: &Mutation<'gc>, object: BitmapDataObject<'gc>) {
            self.0.borrow_mut(mc).avm2_object = Some(object);
        }

        pub fn remove_display_object(&self, mc: &Mutation<'gc>, callback: DisplayObjectWeak<'gc>) {
            // [NA] Removing is a rare operation, whereas insert is often, and iteration is extremely frequent.
            // The list will typically be 0-1 entries long too, so I think retain is fine for quick iteration.
            self.0
                .borrow_mut(mc)
                .display_objects
                .retain(|c| !std::ptr::eq(c.as_ptr(), callback.as_ptr()))
        }

        pub fn add_display_object(&self, mc: &Mutation<'gc>, callback: DisplayObjectWeak<'gc>) {
            self.0.borrow_mut(mc).display_objects.push(callback);
        }

        pub fn render(
            &self,
            smoothing: bool,
            context: &mut RenderContext<'_, 'gc>,
            pixel_snapping: PixelSnapping,
        ) {
            let mut inner_bitmap_data = self.0.borrow_mut(context.gc());
            if inner_bitmap_data.disposed() {
                return;
            }

            // Note - we do a CPU -> GPU sync, but we do *not* do a GPU -> CPU sync
            // (rendering is done on the GPU, so the CPU pixels don't need to be up-to-date).
            inner_bitmap_data.update_dirty_texture(context.renderer);
            let handle = inner_bitmap_data.bitmap_handle(context.renderer);

            context.commands.render_bitmap(
                handle,
                context.transform_stack.transform(),
                smoothing,
                pixel_snapping,
            );
        }

        pub fn can_read(&self, read_area: PixelRegion) -> bool {
            if let DirtyState::GpuModified(_, area) = self.0.borrow().dirty_state {
                !area.intersects(read_area)
            } else {
                true
            }
        }

        #[cfg(feature = "egui")]
        pub fn debug_sync_status(&self) -> std::borrow::Cow<'static, str> {
            match self.0.borrow().dirty_state {
                DirtyState::Clean => std::borrow::Cow::Borrowed("Clean"),
                DirtyState::CpuModified(area) => std::borrow::Cow::Owned(format!(
                    "CPU modified from {}, {} to {}, {}",
                    area.x_min, area.y_min, area.x_max, area.y_max
                )),
                DirtyState::GpuModified(_, area) => std::borrow::Cow::Owned(format!(
                    "GPU modified from {}, {} to {}, {}",
                    area.x_min, area.y_min, area.x_max, area.y_max
                )),
            }
        }

        pub fn is_point_in_bounds(&self, x: i32, y: i32) -> bool {
            x >= 0 && x < self.width() as i32 && y >= 0 && y < self.height() as i32
        }

        pub fn ptr_eq(&self, other: BitmapRawDataWrapper<'gc>) -> bool {
            Gc::ptr_eq(self.0, other.0)
        }
    }
}

pub use wrapper::BitmapRawDataWrapper;

impl std::fmt::Debug for BitmapRawData<'_> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("BitmapRawData")
            .field("dirty_state", &self.dirty_state)
            .field("width", &self.width)
            .field("height", &self.height)
            .field("transparency", &self.transparency)
            .field("disposed", &self.disposed)
            .field("bitmap_handle", &self.bitmap_handle)
            .finish()
    }
}

impl<'gc> BitmapRawData<'gc> {
    pub fn new(width: u32, height: u32, transparency: bool, fill_color: u32) -> Self {
        Self {
            pixels: vec![
                Color::bgra_u32(fill_color).to_premultiplied_alpha(transparency);
                width as usize * height as usize
            ],
            width,
            height,
            transparency,
            disposed: false,
            bitmap_handle: None,
            avm2_object: None,
            display_objects: vec![],
            dirty_state: DirtyState::Clean,
            #[cfg(feature = "egui")]
            egui_texture: Default::default(),
        }
    }

    pub fn new_with_pixels(
        width: u32,
        height: u32,
        transparency: bool,
        pixels: Vec<Color>,
    ) -> Self {
        Self {
            pixels,
            width,
            height,
            transparency,
            bitmap_handle: None,
            avm2_object: None,
            disposed: false,
            dirty_state: DirtyState::Clean,
            display_objects: vec![],
            #[cfg(feature = "egui")]
            egui_texture: Default::default(),
        }
    }

    pub fn disposed(&self) -> bool {
        self.disposed
    }

    pub fn dispose(&mut self) {
        self.width = 0;
        self.height = 0;
        self.pixels = Vec::new(); // free the CPU pixel buffer
        self.bitmap_handle = None;
        // There's no longer a handle to update
        self.dirty_state = DirtyState::Clean;
        self.disposed = true;
    }

    pub fn try_bitmap_handle(
        &mut self,
        renderer: &mut dyn RenderBackend,
    ) -> Result<BitmapHandle, ruffle_render::error::Error> {
        if let Some(ref handle) = self.bitmap_handle {
            return Ok(handle.clone());
        }

        let bitmap = Bitmap::new(
            self.width(),
            self.height(),
            BitmapFormat::Rgba,
            self.pixels_rgba(),
        );
        let bitmap_handle = renderer.register_bitmap(bitmap);
        if let Ok(ref handle) = bitmap_handle {
            self.dirty_state = DirtyState::Clean;
            self.bitmap_handle = Some(handle.clone());
        }
        bitmap_handle
    }

    pub fn bitmap_handle(&mut self, renderer: &mut dyn RenderBackend) -> BitmapHandle {
        self.try_bitmap_handle(renderer)
            .expect("Failed to register bitmap")
    }

    pub fn transparency(&self) -> bool {
        self.transparency
    }

    pub fn dirty_state(&self) -> &DirtyState {
        &self.dirty_state
    }

    pub fn set_gpu_dirty(
        &mut self,
        gc_context: &Mutation<'gc>,
        sync_handle: Box<dyn SyncHandle>,
        region: PixelRegion,
    ) {
        self.dirty_state = DirtyState::GpuModified(sync_handle, region);
        self.inform_display_objects(gc_context);
    }

    pub fn set_cpu_dirty(&mut self, gc_context: &Mutation<'gc>, region: PixelRegion) {
        debug_assert!(region.x_max <= self.width);
        debug_assert!(region.y_max <= self.height);

        #[cfg(feature = "egui")]
        self.egui_texture.borrow_mut().take();

        let inform_changes = match &mut self.dirty_state {
            DirtyState::CpuModified(old_region) => {
                old_region.union(region);
                false
            }
            DirtyState::Clean => {
                self.dirty_state = DirtyState::CpuModified(region);
                true
            }
            DirtyState::GpuModified(_, _) => {
                panic!("Attempted to modify CPU dirty state while GPU sync is in progress!")
            }
        };
        if inform_changes {
            self.inform_display_objects(gc_context);
        }
    }

    pub fn pixels(&self) -> &[Color] {
        &self.pixels
    }

    pub fn pixels_rgba(&self) -> &[u8] {
        Color::slice_as_rgba(&self.pixels)
    }

    pub fn width(&self) -> u32 {
        self.width
    }
    pub fn height(&self) -> u32 {
        self.height
    }

    pub fn is_point_in_bounds(&self, x: i32, y: i32) -> bool {
        x >= 0 && x < self.width() as i32 && y >= 0 && y < self.height() as i32
    }

    #[inline]
    pub fn set_pixel32_raw(&mut self, x: u32, y: u32, color: Color) {
        self.pixels[(x + y * self.width) as usize] = color;
    }

    #[inline]
    pub fn set_pixel32_row_raw(&mut self, x1: u32, x2: u32, y: u32, color: Color) {
        let p1 = (x1 + y * self.width) as usize;
        let p2 = (x2 + y * self.width) as usize;
        let slice = &mut self.pixels[p1..p2];
        slice.fill(color);
    }

    #[inline]
    pub fn fill(&mut self, color: Color) {
        self.pixels.fill(color);
    }

    #[inline]
    pub fn get_pixel32_raw(&self, x: u32, y: u32) -> Color {
        self.pixels[(x + y * self.width()) as usize]
    }

    pub fn raw_pixels_mut(&mut self) -> &mut Vec<Color> {
        &mut self.pixels
    }

    pub fn raw_pixels(&self) -> &[Color] {
        &self.pixels
    }

    // Updates the data stored with our `BitmapHandle` if this `BitmapRawData`
    // is dirty
    pub fn update_dirty_texture(&mut self, renderer: &mut dyn RenderBackend) {
        let handle = self.bitmap_handle(renderer);
        match &self.dirty_state {
            DirtyState::CpuModified(region) => {
                if let Err(e) = renderer.update_texture(
                    &handle,
                    Bitmap::new(
                        self.width(),
                        self.height(),
                        BitmapFormat::Rgba,
                        self.pixels_rgba(),
                    ),
                    *region,
                ) {
                    tracing::error!("Failed to update dirty bitmap {:?}: {:?}", handle, e);
                }
                self.dirty_state = DirtyState::Clean
            }
            DirtyState::Clean | DirtyState::GpuModified(_, _) => {}
        }
    }

    fn inform_display_objects(&self, gc_context: &Mutation<'gc>) {
        for object in &self.display_objects {
            if let Some(object) = object.upgrade(gc_context) {
                object.invalidate_cached_bitmap();
            }
        }
    }

    pub fn object2(&self) -> Option<BitmapDataObject<'gc>> {
        self.avm2_object
    }

    pub fn init_object2(&mut self, object: BitmapDataObject<'gc>) {
        self.avm2_object = Some(object);
    }
}

pub enum IBitmapDrawable<'gc> {
    BitmapData(BitmapData<'gc>),
    DisplayObject(DisplayObject<'gc>),
}

impl IBitmapDrawable<'_> {
    pub fn bounds(&self) -> Rectangle<Twips> {
        match self {
            IBitmapDrawable::BitmapData(bmd) => Rectangle {
                x_min: Twips::ZERO,
                x_max: Twips::from_pixels(bmd.width() as f64),
                y_min: Twips::ZERO,
                y_max: Twips::from_pixels(bmd.height() as f64),
            },
            IBitmapDrawable::DisplayObject(o) => o.bounds(),
        }
    }
}

#[instrument(level = "debug", skip_all)]
fn copy_pixels_to_bitmapdata(
    write: &mut BitmapRawData,
    buffer: &[u8],
    buffer_width: u32,
    area: PixelRegion,
) {
    let buffer_width_pixels = buffer_width / 4;

    for y in area.y_min..area.y_max {
        for x in area.x_min..area.x_max {
            // note: this order of conversions helps llvm realize the index is 4-byte-aligned
            let ind = (((x - area.x_min) + (y - area.y_min) * buffer_width_pixels) as usize) * 4;

            // TODO(mid): optimize this A LOT
            let r = buffer[ind];
            let g = buffer[ind + 1usize];
            let b = buffer[ind + 2usize];
            let a = if write.transparency() {
                buffer[ind + 3usize]
            } else {
                255
            };

            let nc = Color::rgba(r, g, b, a);

            // Ignore the original color entirely - the blending (including alpha)
            // was done by the renderer when it wrote over the previous texture contents.
            write.set_pixel32_raw(x, y, nc);
        }
    }
}

#[derive(Copy, Clone, Debug)]
pub enum ThresholdOperation {
    Equals,
    NotEquals,
    LessThan,
    LessThanOrEquals,
    GreaterThan,
    GreaterThanOrEquals,
}

impl ThresholdOperation {
    pub fn from_wstr(str: &WStr) -> Option<Self> {
        if str == b"==" {
            Some(Self::Equals)
        } else if str == b"!=" {
            Some(Self::NotEquals)
        } else if str == b"<" {
            Some(Self::LessThan)
        } else if str == b"<=" {
            Some(Self::LessThanOrEquals)
        } else if str == b">" {
            Some(Self::GreaterThan)
        } else if str == b">=" {
            Some(Self::GreaterThanOrEquals)
        } else {
            None
        }
    }

    pub fn matches(&self, value: u32, masked_threshold: u32) -> bool {
        match self {
            ThresholdOperation::Equals => value == masked_threshold,
            ThresholdOperation::NotEquals => value != masked_threshold,
            ThresholdOperation::LessThan => value < masked_threshold,
            ThresholdOperation::LessThanOrEquals => value <= masked_threshold,
            ThresholdOperation::GreaterThan => value > masked_threshold,
            ThresholdOperation::GreaterThanOrEquals => value >= masked_threshold,
        }
    }
}
